#include "SQLExprTableSource.h"
#include "../../SQLUtils.h"

#include "../../../Exception/IllegalArgumentException.h"

#include "../../../utils/instanceof.h"
#include "../../../utils/FnvHash.h"
#include "../../../utils/ifeq.h"
#include "../../../utils/log.h"

#include "../expr/SQLAllColumnExpr.h"
#include "../../visitor/SQLASTVisitor.h"

#include "../../repository/SchemaObject.h"

SETCALSS(SQLExprTableSource);

SQLExprTableSource::SQLExprTableSource()
{
  partitions = std::make_shared<std::list<SQLName_ptr>>();
  columns = std::make_shared<std::list<SQLName_ptr>>();
}

SQLExprTableSource::SQLExprTableSource(string_ptr tableName)
    : SQLExprTableSource(SQLUtils::toSQLExpr(tableName), make_string_ptr(""))
{
}

SQLExprTableSource::SQLExprTableSource(SQLExpr_ptr expr)
    : SQLExprTableSource(expr, make_string_ptr(""))
{
}

SQLExprTableSource::SQLExprTableSource(SQLExpr_ptr expr, string_ptr alias)
{
  partitions = std::make_shared<std::list<SQLName_ptr>>();
  columns = std::make_shared<std::list<SQLName_ptr>>();
  this->setExpr(expr);
  this->setAlias(alias);
}

SQLExpr_ptr SQLExprTableSource::getExpr()
{
  return this->expr;
}

void SQLExprTableSource::setExpr(SQLExpr_ptr x)
{
  if (x != NULL)
  {
    // x->setParent(SharedObject(SQLExprTableSource));
    x->setParent(SharedObject(SQLExprTableSource));
  }
  this->expr = x;
}

void SQLExprTableSource::setExpr(string_ptr name)
{
  this->setExpr(SQLIdentifierExpr_ptr(new SQLIdentifierExpr((name))));
}

SQLTableSampling_ptr SQLExprTableSource::getSampling()
{
  return sampling;
}

void SQLExprTableSource::setSampling(SQLTableSampling_ptr x)
{
  if (x != NULL)
  {
    x->setParent(SharedObject(SQLExprTableSource));
  }
  this->sampling = x;
}

SQLName_ptr SQLExprTableSource::getName()
{
  // if (expr instanceof SQLName) {
  //     return (SQLName) expr;
  // }
  return NULL;
}

string_ptr SQLExprTableSource::getTableName(BOOL_ptr normalize)
{
  string_ptr tableName = getTableName();
  if (normalize)
  {
    return SQLUtils::normalize(tableName);
  }
  return tableName;
}

string_ptr SQLExprTableSource::getTableName()
{
  if (expr == NULL)
  {
    return make_string_ptr("");
  }

  if (instanceof <SQLExpr, SQLIdentifierExpr>(expr))
  {
    return ((std::dynamic_pointer_cast<SQLIdentifierExpr>(expr))->getName());
  }

  if (instanceof <SQLExpr, SQLPropertyExpr>(expr))
  {
    return ((std::dynamic_pointer_cast<SQLPropertyExpr>(expr))->getSimpleName());
  }

  return make_string_ptr("");
}

string_ptr SQLExprTableSource::getSchema()
{
  LOG_INFO <<  ":";
  if (expr == NULL)
  {
    return make_string_ptr("");
  }
  LOG_INFO << ":";
  if (instanceof <SQLExpr, SQLPropertyExpr>(expr))
  {
    SQLExpr_ptr owner = (std::dynamic_pointer_cast<SQLPropertyExpr>(expr))->getOwner();
    if (instanceof <SQLExpr, SQLIdentifierExpr>(owner))
    {
      return ((std::dynamic_pointer_cast<SQLIdentifierExpr>(owner))->getName());
    }
    LOG_INFO << ":";
    if (instanceof <SQLExpr, SQLPropertyExpr>(owner))
    {
      return ((std::dynamic_pointer_cast<SQLPropertyExpr>(owner))->getSimpleName());
    }
    LOG_INFO << ":";
    if (instanceof <SQLExpr, SQLAllColumnExpr>(owner))
    {
      return make_string_ptr("*");
    }
    LOG_INFO << ":";
    return make_string_ptr("");
  }
  LOG_INFO << ":";
  return make_string_ptr("");
}

string_ptr SQLExprTableSource::getCatalog()
{
  // if (expr instanceof SQLPropertyExpr) {
  //     SQLExpr_ptr owner = ((SQLPropertyExpr) expr).getOwner();

  //     if (owner instanceof SQLPropertyExpr) {
  //         SQLExpr_ptr catalogExpr = ((SQLPropertyExpr) owner).getOwner();
  //         if (catalogExpr instanceof SQLIdentifierExpr) {
  //             return ((SQLIdentifierExpr) catalogExpr).getName();
  //         }
  //     }

  //     return NULL;
  // }

  return NULL;
}

BOOL_ptr SQLExprTableSource::setCatalog(string_ptr catalog)
{
  // if (expr instanceof SQLPropertyExpr) {
  //     SQLPropertyExpr propertyExpr = (SQLPropertyExpr) expr;
  //     SQLExpr_ptr owner = propertyExpr.getOwner();
  //     if (owner instanceof SQLIdentifierExpr) {
  //         if (catalog == NULL) {
  //             return BOOL::FALSE;
  //         }

  //         propertyExpr.setOwner(
  //                 new SQLPropertyExpr(
  //                         catalog,
  //                         ((SQLIdentifierExpr) owner).getName()
  //                 )
  //         );
  //         return BOOL::TRUE;
  //     } else if (owner instanceof SQLPropertyExpr) {
  //         SQLPropertyExpr propertyOwner = (SQLPropertyExpr) owner;
  //         final SQLExpr_ptr propertyOwnerOwner = propertyOwner.getOwner();
  //         if (propertyOwnerOwner instanceof SQLIdentifierExpr) {
  //             if (catalog == NULL) {
  //                 propertyExpr.setOwner(((SQLIdentifierExpr) propertyOwnerOwner).getName());
  //             } else {
  //                 propertyOwner.setOwner(
  //                         new SQLIdentifierExpr(catalog));
  //             }
  //             return BOOL::TRUE;
  //         }
  //     }
  // }

  return BOOL::FALSE;
}

void SQLExprTableSource::setCatalog(string_ptr catalog, string_ptr schema)
{
  if (catalog->empty())
  {
    throw new IllegalArgumentException(make_string_ptr("catalog is NULL."));
  }

  if (schema->empty())
  {
    throw new IllegalArgumentException(make_string_ptr("schema is NULL."));
  }

  setSchema(schema);
  setCatalog(catalog);
}

void SQLExprTableSource::setSchema(string_ptr schema)
{
  // if (expr instanceof SQLPropertyExpr) {
  //     SQLPropertyExpr propertyExpr = (SQLPropertyExpr) expr;
  //     if (StringUtils.isEmpty(schema)) {
  //         this->setExpr(new SQLIdentifierExpr(propertyExpr.getName()));
  //     } else {
  //         propertyExpr.setOwner(schema);
  //     }
  // } else {
  //     if (StringUtils.isEmpty(schema)) {
  //         return;
  //     }

  //     string_ptr  ident = ((SQLIdentifierExpr) expr).getName();
  //     this->setExpr(new SQLPropertyExpr(schema, ident));
  // }
}

void SQLExprTableSource::setSimpleName(string_ptr name)
{
  // if (StringUtils.isEmpty(name)) {
  //     throw new IllegalArgumentException("schema is empty.");
  // }
  // if (expr == NULL) {
  //     expr = new SQLIdentifierExpr(name);
  // } else if (expr instanceof SQLPropertyExpr) {
  //     ((SQLPropertyExpr) expr).setName(name);
  // } else {
  //     expr = new SQLIdentifierExpr(name);
  // }
}

SQLName_list_ptr SQLExprTableSource::getPartitions()
{
  // if (this->partitions == NULL) {
  //     this->partitions = new ArrayList<SQLName>(2);
  // }

  return partitions;
}

int SQLExprTableSource::getPartitionSize()
{
  if (this->partitions->empty())
  {
    return 0;
  }
  return this->partitions->size();
}

void SQLExprTableSource::addPartition(SQLName_ptr partition)
{
  if (partition != NULL)
  {
    partition->setParent(SharedObject(SQLExprTableSource));
  }

  if (this->partitions == NULL)
  {
    this->partitions = std::shared_ptr<std::__cxx11::list<SQLName_ptr>>();
  }
  this->partitions->push_back(partition);
}

void SQLExprTableSource::accept0(SQLASTVisitor_ptr visitor)
{
  if (visitor->visit(SharedObject(SQLExprTableSource)))
  {
    if (expr != NULL)
    {
      expr->accept(visitor);
    }

    if (sampling != NULL)
    {
      sampling->accept(visitor);
    }
  }
  visitor->endVisit(SharedObject(SQLExprTableSource));
}

// string_ptr  computeAlias() {
//     string_ptr  alias = this->getAlias();

//     if (alias == NULL) {
//         if (expr instanceof SQLName) {
//             alias = ((SQLName) expr).getSimpleName();
//         }
//     }

//     return SQLUtils.normalize(alias);
// }

// SQLExprTableSource_ptr SQLExprTableSource::clone()
SQLObject_ptr SQLExprTableSource::clone()
{
  SQLObject_ptr clone_tmp = SQLExprTableSource_ptr(new SQLExprTableSource());
  SQLExprTableSource_ptr clone_res = std::dynamic_pointer_cast<SQLExprTableSource>(clone_tmp);
  // SQLExprTableSource_ptr x = new SQLExprTableSource();
  cloneTo(std::dynamic_pointer_cast<SQLExprTableSource>(clone_res));
  return clone_res;
}

void SQLExprTableSource::cloneTo(SQLExprTableSource_ptr x)
{
  x->alias = alias;

  if (expr != NULL)
  {
    x->setExpr(std::dynamic_pointer_cast<SQLExpr>(expr->clone()));
  }

  if (partitions->empty())
  {
    for (SQLName_ptr p : *partitions)
    {
      SQLName_ptr p1 = std::dynamic_pointer_cast<SQLName>(p->clone());
      x->addPartition(p1);
    }
  }

  // if (schemaObject != NULL) {
  //     x->setSchemaObject(schemaObject);
  // }

  if (columns->empty())
  {
    // x->columns = new ArrayList<SQLName>(columns.size());

    for (SQLName_ptr column : *columns)
    {
      SQLName_ptr clonedColumn = std::dynamic_pointer_cast<SQLName>(column->clone());
      clonedColumn->setParent(x);
      x->columns->push_back(clonedColumn);
    }
  }
}

SQLName_list_ptr SQLExprTableSource::getColumns()
{
  // if (columns == NULL) {
  //     columns = new ArrayList<SQLName>(2);
  // }
  return columns;
}

SQLName_list_ptr SQLExprTableSource::getColumnsDirect()
{
  return columns;
}

SchemaObject_ptr SQLExprTableSource::getSchemaObject()
{
  return schemaObject;
}

void SQLExprTableSource::setSchemaObject(SchemaObject_ptr schemaObject)
{
  this->schemaObject = schemaObject;
}

// BOOL_ptr containsAlias(string_ptr  alias) {
//     long hashCode64 = FnvHash.hashCode64(alias);

//     return containsAlias(hashCode64);
// }

BOOL_ptr SQLExprTableSource::containsAlias(long aliasHash)
{
  if (this->aliasHashCode64() == aliasHash)
  {
    return BOOL::TRUE;
  }

  // if (expr instanceof SQLPropertyExpr) {
  //     long exprNameHash = ((SQLPropertyExpr) expr).hashCode64();
  //     if (exprNameHash == aliasHash) {
  //         return BOOL::TRUE;
  //     }
  // }

  // if (expr instanceof SQLName) {
  //     long exprNameHash = ((SQLName) expr).nameHashCode64();
  //     return exprNameHash == aliasHash;
  // }

  return BOOL::FALSE;
}

// SQLColumnDefinition* SQLExprTableSource::findColumn(string_ptr  columnName) {
//     if (columnName.empty()) {
//         return NULL;
//     }

//     long hash = FnvHash::hashCode64(columnName);
//     return findColumn(hash);
// }

// SQLColumnDefinition* SQLExprTableSource::findColumn(long columnNameHash) {
//     // SQLObject object = resolveColum(columnNameHash);
//     // if (object instanceof SQLColumnDefinition) {
//     //     return (SQLColumnDefinition) object;
//     // }
//     return NULL;
// }

// SQLObject* resolveColum(long columnNameHash) {
//     if (schemaObject != NULL) {
//         SQLStatement stmt = schemaObject.getStatement();
//         if (stmt instanceof SQLCreateTableStatement) {
//             SQLCreateTableStatement createTableStmt = (SQLCreateTableStatement) stmt;
//             return createTableStmt.findColumn(columnNameHash);
//         }
//     }

//     SQLObject resolvedOwnerObject = NULL;
//     if (expr instanceof SQLIdentifierExpr) {
//         resolvedOwnerObject = ((SQLIdentifierExpr) expr).getResolvedOwnerObject();
//     }

//     if (resolvedOwnerObject == NULL) {
//         return resolvedOwnerObject;
//     }

//     if (resolvedOwnerObject instanceof SQLWithSubqueryClause.Entry) {
//         final SQLSelect subQuery = ((SQLWithSubqueryClause.Entry) resolvedOwnerObject)
//                 .getSubQuery();
//         if (subQuery == NULL) {
//             return NULL;
//         }

//         final SQLSelectQueryBlock firstQueryBlock = subQuery.getFirstQueryBlock();
//         if (firstQueryBlock == NULL) {
//             return NULL;
//         }

//         SQLSelectItem selectItem = firstQueryBlock.findSelectItem(columnNameHash);
//         if (selectItem != NULL) {
//             return selectItem;
//         }
//     }
//     return NULL;
// }

SQLTableSource_ptr SQLExprTableSource::findTableSourceWithColumn(string_ptr columnName)
{
  if (columnName->empty())
  {
    return NULL;
  }

  long hash = FnvHash::hashCode64(columnName);
  return findTableSourceWithColumn(hash, columnName, 0);
}

SQLTableSource_ptr SQLExprTableSource::findTableSourceWithColumn(long columnName_hash, string_ptr name, int option)
{
  // if (schemaObject != NULL) {
  //     SQLStatement_ptr  stmt = schemaObject->getStatement();
  //     // if (stmt instanceof SQLCreateTableStatement) {
  //     if (instanceof<SQLStatement, SQLCreateTableStatement>(stmt)) {
  //         SQLCreateTableStatement* createTableStmt = (SQLCreateTableStatement*) stmt;
  //         if (createTableStmt->findColumn(columnName_hash) != NULL) {
  //             return this;
  //         }
  //     }
  // }

  // if (expr instanceof SQLIdentifierExpr) {
  if (instanceof <SQLExpr, SQLIdentifierExpr>(expr))
  {
    // SQLTableSource_ptr  tableSource = (dynamic_cast<SQLIdentifierExpr*>(expr))->getResolvedTableSource();
    // if (tableSource != NULL) {
    //     return this;
    // }
  }

  return NULL;
}

SQLTableSource_ptr SQLExprTableSource::findTableSource(long alias_hash)
{
  if (alias_hash == 0)
  {
    return NULL;
  }

  if (aliasHashCode64() == alias_hash)
  {
    return SharedObject(SQLExprTableSource);
  }

  // if (expr instanceof SQLName) {
  if (instanceof <SQLExpr, SQLName>(expr))
  {
    long exprNameHash = (std::dynamic_pointer_cast<SQLName>(expr))->nameHashCode64();
    if (exprNameHash == alias_hash)
    {
      return SharedObject(SQLExprTableSource);
    }
  }

  // if (expr instanceof SQLPropertyExpr) {
  //     long hash = ((SQLPropertyExpr) expr).hashCode64();
  //     if (hash == alias_hash) {
  //         return this;
  //     }
  // }

  return NULL;
}

BOOL_ptr SQLExprTableSource::replace(SQLExpr_ptr expr, SQLExpr_ptr target)
{
  if (expr == this->expr)
  {
    this->setExpr(target);
    return BOOL::TRUE;
  }

  if (partitions->empty())
  {
    for (int i = 0; i < partitions->size(); i++)
    {
      // if (partitions.get(i) == expr) {
      //     target->setParent(this);
      //     partitions->set(i, dynamic_cast<SQLName*>(target));
      //     return BOOL::TRUE;
      // }
    }
  }

  return BOOL::FALSE;
}

long SQLExprTableSource::aliasHashCode64()
{
  if (alias != nullptr)
  {
    if (aliasHashCode64_ == 0)
    {
      aliasHashCode64_ = FnvHash::hashCode64(alias);
    }
    return aliasHashCode64_;
  }

  if (instanceof <SQLExpr, SQLName>(expr))
  {
    return (std::dynamic_pointer_cast<SQLName>(expr))->nameHashCode64();
  }

  return 0;
}

bool SQLExprTableSource::equals(Object_ptr o)
{
  if (this == o.get())
  {
    return true;
  }
  if (o == NULL || getClass() != o->getClass())
  {
    return false;
  }
  if (!SQLExprTableSource::equals(o)) {
      return false;
  }

  SQLExprTableSource_ptr that = std::dynamic_pointer_cast<SQLExprTableSource>(o);

  if (expr != NULL ? !(expr == that->expr) : that->expr != NULL)
  {
    return false;
  }
  if (partitions != NULL ? !IFEQ::ifeq<SQLName_ptr>( partitions,that->partitions) : that->partitions != NULL) {
      return false;
  }
  if (sampling != NULL ? !sampling->equals(that->sampling) : that->sampling != NULL) {
      return false;
  }
  if (schemaObject != NULL ? !schemaObject->equals(that->schemaObject) : that->schemaObject != NULL) {
      return false;
  }
  return columns->empty() ? IFEQ::ifeq<SQLName_ptr>(columns,that->columns) : that->columns->empty();

}

int SQLExprTableSource::hashCode()
{
  // int result = super->hashCode();
  int result = 1;
  result = 31 * result + (expr != NULL ? expr->hashCode() : 0);
  // result = 31 * result + (partitions != NULL ? partitions->hashCode() : 0);
  // result = 31 * result + (sampling != NULL ? sampling->hashCode() : 0);
  // result = 31 * result + (schemaObject != NULL ? schemaObject->hashCode() : 0);
  // result = 31 * result + (columns != NULL ? columns->hashCode() : 0);
  return result;
}
